﻿using NullCommander.Properties;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;

namespace NullCommander
{
    public partial class frmTextEditor : Form
    {
        private static frmTextEditor self;
        // Исттория открытых файлов.
        private static List<String> historyOpen = new List<String>();
        // Список расширений, которые будут открываться во внутреннем текстовом редакторе.
        private static String[] supportedFormats = new String[] { ".txt", ".log", ".cs" };
        // Нумерация для создания новых файлов.
        private static Int32 newDocumentCount = 0;
        // variable to trace text to print for pagination
        private Int32 FirstCharOnPage;
        // Список ключевых слов для подсветки.
        private static String[] keywords = new[] { 
            "abstract", "as", "base", "bool", "break", "byte", "case", "catch", 
            "char", "checked", "class", "const", "continue", "decimal", "default", 
            "delegate", "do", "double", "else", "enum", "event", "explicit", 
            "extern", "false", "finally", "fixed", "float", "for", "foreach", 
            "goto", "if", "implicit", "in", "int", "interface", "internal", 
            "is", "lock", "long", "namespace", "new", "null", "object", 
            "operator", "out", "override", "params", "private", "protected", 
            "public", "readonly", "ref", "return", "sbyte", "sealed", "short", 
            "sizeof", "stackalloc", "static", "string", "struct", "switch", 
            "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", 
            "unsafe", "ushort", "using", "virtual", "void", "volatile", "while", 
            "__arglist", "__makeref", "__reftype", "__refvalue", "add", "alias", 
            "ascending", "assembly", "by", "descending", "dynamic", "equals", 
            "field", "from", "get", "global", "group", "into", "join", "let", 
            "method", "module", "on", "orderby", "param", "partial", "property", 
            "remove", "select", "set", "type", "typevar", "value", "var", "where", "yield" };

        private const String RICH_CONTROL_NAME = "richControl";
        private const Int32 HISTORY_MAX_FILES = 10;

        private frmTextEditor()
        {
            InitializeComponent();

            // Загрузка фильтра в диалоги.
            var filter = "Все форматы|*.*";
            foreach (var str in supportedFormats)
                filter += String.Format("|{0}|*{0}", str);
            openFileDialog.Filter = filter;
            saveFileDialog.Filter = filter;
        }

        #region Статические методы
        /// <summary>
        /// Может ли файл открыть в текстовом редакторе?
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static Boolean IsSupportedFormat(String path)
        {
            return Array.IndexOf(supportedFormats, Path.GetExtension(path)) != -1;
        }

        /// <summary>
        /// Очистка истории от несуществующих файлов.
        /// </summary>
        private static void CleanHistoryFiles()
        {
            for (var i = historyOpen.Count - 1; i >= 0; i--)
                if (!File.Exists(historyOpen[i]))
                    historyOpen.RemoveAt(i);
        }

        /// <summary>
        /// Открытие файла с созданием экземпляра редактора (при надобности)
        /// </summary>
        /// <param name="path"></param>
        public static void OpenTextFile(String path)
        {
            // Создаем форму, если она не существует.
            if (self == null)
                self = new frmTextEditor();
            // Открытваем файл.
            self.DoOpenTextFile(path, true);
            // Показываем форму.
            self.Show();
            // Перемещаем форму на передний план.
            self.BringToFront();
            return;
        }
        #endregion

        #region Разное
        /// <summary>
        /// Добавление файла в историю открытых файлов.
        /// </summary>
        /// <param name="path"></param>
        private void AddToHistory(String path)
        {
            // Упорядочиваем историю открытых файлов.
            // Проверка истории и очистка несуществующих файлов.
            CleanHistoryFiles();
            // Был ли открыт этот файл ранее?
            var index = historyOpen.IndexOf(path);
            // Если файл ранее не открывался.
            if (index == -1)
            {
                // Добавляем файл в историю.
                historyOpen.Add(path);
                // История хранит не более HISTORY_MAX_FILES файлов.
                if (historyOpen.Count > HISTORY_MAX_FILES)
                    historyOpen.RemoveAt(0);
            }
            else
            {
                if (index != historyOpen.Count - 1)
                {
                    // Переносим файл в конец списка.
                    var str = historyOpen[index];
                    historyOpen.RemoveAt(index);
                    historyOpen.Add(str);
                }
            }
        }

        /// <summary>
        /// Создание новой вкладки.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private TabPage CreateNewTab(String path)
        {
            // Добавляем новую вкладку.
            tabControl.TabPages.Add(path, Path.GetFileName(path));
            // Ищем ее по ключу.
            var tab = tabControl.TabPages[path];
            if (tab == null)
                return null;
            try
            {
                tab.SuspendLayout();
                // Создаем текстовый редактор.
                var richTextBox = new RichTextBox();
                // Задаем имя редактору.
                richTextBox.Name = RICH_CONTROL_NAME;
                // Можно ли переносить слова.
                richTextBox.WordWrap = itemWordWrap.Checked;
                // Текущий шрифт.
                richTextBox.Font = fontDialog.Font;
                // Запрещаем скрывать выделение.
                richTextBox.HideSelection = false;
                // Обработчик изменения позиции курсора.
                richTextBox.SelectionChanged += richTextBox_SelectionChanged;
                // Обработчик изменения текста.
                richTextBox.TextChanged += richTextBox_TextChanged;
                // Обработчик изменения шрифта.
                richTextBox.FontChanged += richTextBox_FontChanged;
                // Растягиваем его на всего родителя.
                richTextBox.Dock = DockStyle.Fill;
                // Создаем объект, содержащий дополнительные данные для редактора. 
                richTextBox.Tag = new RichTextBoxData(); 
                // Добавляем редактор на вкладку.
                tab.Controls.Add(richTextBox);
                tab.ResumeLayout(false);
                tab.PerformLayout();
            }
            catch
            {
                tabControl.TabPages.Remove(tab);
                throw;
            }
            return tab;
        }

        /// <summary>
        /// Открытие файла.
        /// </summary>
        /// <param name="path"></param>
        private void DoOpenTextFile(String path, Boolean needLoad)
        {
            // Проверяем, не открыть ли уже данный файл.
            var tab = tabControl.TabPages[path];
            // Если файл еще не открыт - создаем для него вкладку и открываем его.
            if (tab == null)
            {
                tab = CreateNewTab(path);
                if (tab == null)
                    return;
                AddToHistory(path);
                // Если необходимо - загружаем содержимое файла.
                if (needLoad)
                {
                    var richTextBox = tab.Controls[RICH_CONTROL_NAME] as RichTextBox;
                    if (richTextBox != null)
                        LoadTextToRichTextBox(richTextBox, File.ReadAllText(path, Encoding.UTF8));
                }
            }
            // Выделяем таб.
            tabControl.SelectedTab = tab;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="kind"></param>
        private void LoadTextToRichTextBox(RichTextBox richTextBox, String text)
        {
            // Загружаем текст.
            richTextBox.Text = text;
            // Полностью проверяем текст и подсвечиваем ключевые слова.
            HighlightText(richTextBox);
        }

        private void DoFind(FindKind kind)
        {
            pnlFind.Visible = kind != FindKind.None;
            lblReplace.Visible = kind == FindKind.Replace;
            edtReplace.Visible = kind == FindKind.Replace;
            btnReplace.Visible = kind == FindKind.Replace;
            btnReplaceAll.Visible = kind == FindKind.Replace;
            if (pnlFind.Visible)
                edtFind.Focus();
        }

        private String GetCurrentFile()
        {
            return tabControl.SelectedTab != null ? tabControl.SelectedTab.Name : String.Empty;
        }

        private RichTextBox GetCurrentRichTextBox()
        {
            return tabControl.SelectedTab != null ? tabControl.SelectedTab.Controls[RICH_CONTROL_NAME] as RichTextBox : null;
        }

        private String GetCurrentText()
        {
            var richTextBox = GetCurrentRichTextBox();
            return richTextBox != null ? richTextBox.Text : String.Empty;
        }

        private Boolean SaveFile(String path, String text)
        {
            var result = true;
            try
            {
                File.WriteAllText(path, text, Encoding.UTF8);
            }
            catch (Exception ex)
            {
                result = false;
                MessageBox.Show(String.Format(Resources.ERROR_SAVE_FILE, path, ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            return result;
        }

        private void frmTextEditor_FormClosed(object sender, FormClosedEventArgs e)
        {
            tabControl.TabPages.Clear();
            self = null;
        }
        #endregion

        #region События меню 

        #region Файл
        private void itemFileGroup_DropDownOpening(object sender, EventArgs e)
        {
            // Проверка истории и очистка несуществующих файлов.
            CleanHistoryFiles();
            // Текущий открытый файл.
            var currentFileName = GetCurrentFile();
            // Очищаем старые значения.
            itemHistory.DropDownItems.Clear();
            foreach (var str in historyOpen)
                if (String.Compare(currentFileName, str, true) != 0)
                {
                    var item = itemHistory.DropDownItems.Add(str);
                    item.Click += itemOpenSubItem_Click;
                }
        }
        
        private void itemCreate_Click(object sender, EventArgs e)
        {
            try
            {
                DoOpenTextFile(String.Format("Новый документ {0}", ++newDocumentCount), false);
            }
            catch (Exception ex)
            {
                MessageBox.Show(String.Format(Resources.ERROR_CREATE_DOCUMENT, ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void itemOpen_Click(object sender, EventArgs e)
        {
            if (openFileDialog.ShowDialog() == DialogResult.OK)
                try
                {
                    DoOpenTextFile(openFileDialog.FileName, true);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(String.Format(Resources.ERROR_OPEN_FILE, saveFileDialog.FileName, ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }

        }

        private void itemSave_Click(object sender, EventArgs e)
        {
            // Текущий открытый файл.
            var currentFileName = GetCurrentFile();
            // Если файл не существует, надо предложить пользователю выбрать место куда сохранить файл.
            if (!File.Exists(currentFileName))
                itemSaveAs_Click(sender, e);
            else
                SaveFile(currentFileName, GetCurrentText());
        }

        private void itemSaveAs_Click(object sender, EventArgs e)
        {
            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                var path = saveFileDialog.FileName;
                // Пытаемся сохранить файл. 
                if (SaveFile(path, GetCurrentText()))
                {
                    // Добавляем файл в историю.
                    AddToHistory(path);
                    var tab = tabControl.SelectedTab;
                    if (tab != null)
                    {
                        // Переименовываем вкладку.
                        tab.Name = path;
                        tab.Text = Path.GetFileName(path);
                        var richEditBox = tab.Controls[RICH_CONTROL_NAME] as RichTextBox;
                        if (richEditBox != null)
                            HighlightText(richEditBox);
                    }
                }
            }
        }

        private void itemOpenSubItem_Click(object sender, EventArgs e)
        {
            var item = sender as ToolStripMenuItem;
            if (item != null)
                try
                {
                    DoOpenTextFile(item.Text, true);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(String.Format(Resources.ERROR_OPEN_FILE, item.Text, ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
        }

        private void itemPrint_Click(object sender, EventArgs e)
        {
            if (printDialog.ShowDialog() == DialogResult.OK)
                try
                {
                    printDocument.Print();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(String.Format(Resources.ERROR_OPEN_FILE, saveFileDialog.FileName, ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
        }


        private void itemClose_Click(object sender, EventArgs e)
        {
            Close();
        }
        #endregion

        #region Поиск
        private void itemFind_Click(object sender, EventArgs e)
        {
            DoFind(FindKind.Find);
        }

        private void itemReplace_Click(object sender, EventArgs e)
        {
            DoFind(FindKind.Replace);
        }
        #endregion

        #region Вид
        private void itemFont_Click(object sender, EventArgs e)
        {
            if (fontDialog.ShowDialog() == DialogResult.OK)
                foreach (TabPage tab in tabControl.TabPages)
                {
                    var richTextBox = tab.Controls[RICH_CONTROL_NAME] as RichTextBox;
                    if (richTextBox != null)
                        richTextBox.Font = fontDialog.Font;
                }
        }

        private void itemWordWrap_CheckedChanged(object sender, EventArgs e)
        {
            foreach (TabPage tab in tabControl.TabPages)
            {
                var richTextBox = tab.Controls[RICH_CONTROL_NAME] as RichTextBox;
                if (richTextBox != null)
                    richTextBox.WordWrap = itemWordWrap.Checked;
            }
        }
        #endregion

        #region Шифрование
        private void itemXorCrypt_Click(object sender, EventArgs e)
        {
            var richTextBox = GetCurrentRichTextBox();
            if (richTextBox == null)
                return;
            var key = String.Empty;
            if (frmInputQuery.InputQuery("Xor", Resources.KEY_FOR_CRYPT, ref key))
            {
                LoadTextToRichTextBox(richTextBox, XorCrypt(richTextBox.Text, key));
            }
        }

        private void itemXorDecrypt_Click(object sender, EventArgs e)
        {
            var richTextBox = GetCurrentRichTextBox();
            if (richTextBox == null)
                return;
            var key = String.Empty;
            if (frmInputQuery.InputQuery("Xor", Resources.KEY_FOR_DECRYPT, ref key))
            {
                LoadTextToRichTextBox(richTextBox, XorDecrypt(richTextBox.Text, key));
            }
        }

        private void itemAES256Crypt_Click(object sender, EventArgs e)
        {
            var richTextBox = GetCurrentRichTextBox();
            if (richTextBox == null)
                return;
            var key = String.Empty;
            if (frmInputQuery.InputQuery("AES256", Resources.KEY_FOR_CRYPT, ref key))
            {
                LoadTextToRichTextBox(richTextBox, AesCrypt(richTextBox.Text, key));
            }
        }

        private void itemAES256Decrypt_Click(object sender, EventArgs e)
        {
            var richTextBox = GetCurrentRichTextBox();
            if (richTextBox == null)
                return;
            var key = String.Empty;
            if (frmInputQuery.InputQuery("AES256", Resources.KEY_FOR_DECRYPT, ref key))
            {
                LoadTextToRichTextBox(richTextBox, AesDecrypt(richTextBox.Text, key));
            }
        }
        #endregion

        #endregion

        #region События RichTextBox
        private void richTextBox_SelectionChanged(object sender, EventArgs e)
        {
            var richTextBox = sender as RichTextBox;
            if (richTextBox == null)
                statusBarLabel.Text = String.Empty;
            else
            {
                CheckForModification(richTextBox);
                Int32 position = richTextBox.SelectionStart;
                Int32 line = richTextBox.GetLineFromCharIndex(position);
                position -= richTextBox.GetFirstCharIndexFromLine(line);
                statusBarLabel.Text = String.Format("Строка: {0} Позиция: {1}", ++line, ++position);
            }
        }

        private void richTextBox_TextChanged(object sender, EventArgs e)
        {
            var richTextBox = sender as RichTextBox;
            if (richTextBox != null)
                CheckForModification(richTextBox);
        }

        private void richTextBox_FontChanged(object sender, EventArgs e)
        {
            var richTextBox = sender as RichTextBox;
            if (richTextBox != null)
                HighlightText(richTextBox);
        }
        #endregion

        #region Панель поиска/замены
        private void edtFind_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\r')
            {
                btnFind_Click(btnFind, new EventArgs());
                e.Handled = true;
            }
        }

        private void edtReplace_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\r')
            {
                btnFind_Click(btnReplace, new EventArgs());
                e.Handled = true;
            }
        }

        private void btnReplaceAll_Click(object sender, EventArgs e)
        {
            var richTextBox = GetCurrentRichTextBox();
            if (richTextBox == null)
                return;
            var findOption = RichTextBoxFinds.None;
            if (!cbxIgnoreCase.Checked)
                findOption |= RichTextBoxFinds.MatchCase;
            if (cbxWholeWord.Checked)
                findOption |= RichTextBoxFinds.WholeWord;

            var pos = 0;
            var findText = edtFind.Text;
            var replaceText = edtReplace.Text;
            var foundCount = 0;
            // Ищем, пока искомая комбинация встречается в тексте.
            while ((pos = richTextBox.Find(findText, pos, findOption)) >= 0)
            {
                // Выделяем найденный текст.
                richTextBox.Select(pos, findText.Length);
                // Заменяем найденный текст.
                richTextBox.SelectedText = replaceText;
                // Обновляем стартовую позицию поиска.
                pos = richTextBox.SelectionStart;
                // Увеличиваем счетчик замен.
                foundCount++;
            }
            if (foundCount == 0)
                MessageBox.Show(Resources.TEXT_NOT_FOUND, Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
            else
                MessageBox.Show(String.Format(Resources.REPLACE_COUNT, foundCount), Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void btnFind_Click(object sender, EventArgs e)
        {
            var richTextBox = GetCurrentRichTextBox();
            if (richTextBox == null)
                return;
            var findOption = RichTextBoxFinds.None;
            if (!cbxIgnoreCase.Checked)
                findOption |= RichTextBoxFinds.MatchCase;
            if (cbxWholeWord.Checked)
                findOption |= RichTextBoxFinds.WholeWord;
            var startPos = richTextBox.SelectionStart + richTextBox.SelectionLength;
            var findText = edtFind.Text;
            var pos = richTextBox.Find(findText, startPos, findOption);
            if (pos < 0)
            {
                MessageBox.Show(Resources.TEXT_NOT_FOUND, Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                // Выделяем найденный текст.
                richTextBox.Select(pos, findText.Length);
                // Скроллим до выделенного текста.
                richTextBox.ScrollToCaret();
                // Заменяем найденный текст.
                if (sender == btnReplace)
                    richTextBox.SelectedText = edtReplace.Text;
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            DoFind(FindKind.None);
        }
        #endregion

        #region Алгоритмы шифрования

        private Byte[] SimpleXor(Byte[] source, Byte[] key)
        {
            var length = key.Length;
            for (var i = 0; i < source.Length; i++)
                source[i] = (Byte)(source[i] ^ key[i % length]);
            return source;
        }

        private String XorCrypt(String source, String key)
        {
            // Получаем строку в виде набора байт.
            var sourceArray = Encoding.UTF8.GetBytes(source);
            // Получаем ключ в виде набора байт.
            var keyArray = Encoding.UTF8.GetBytes(key);
            return Convert.ToBase64String(SimpleXor(sourceArray, keyArray));
        }

        private String XorDecrypt(String source, String key)
        {
            // Получаем строку в виде набора байт, перед этим преобразовывая ее из base64.
            var sourceArray = Convert.FromBase64String(source);
            // Получаем ключ в виде набора байт.
            var keyArray = Encoding.UTF8.GetBytes(key);
            return Encoding.UTF8.GetString(SimpleXor(sourceArray, keyArray));
        }

        private static String AesCrypt(String source, String key)
        {
            // Получаем строку в виде набора байт.
            var sourceArray = Encoding.UTF8.GetBytes(source);
            // Получаем ключ в виде набора байт.
            var keyArray = Encoding.UTF8.GetBytes(key);
            // Создаем экземпляр объекта AES.
            var AES = new AesCryptoServiceProvider();
            // Ключ из sha256 пароля.
            var sha256 = SHA256Managed.Create();
            // Устанавливаем ключ. 
            AES.Key = sha256.ComputeHash(keyArray);
            // Вектор инициализации из md5 ключа.
            var md5 = MD5.Create();
            // Устанавливаем вектор инициализации.
            AES.IV = md5.ComputeHash(keyArray);
            // Создаем криптор.
            ICryptoTransform transform = AES.CreateEncryptor();
            // Криптуем строку.
            sourceArray = transform.TransformFinalBlock(sourceArray, 0, sourceArray.Length);
            // Преобразовываем полученную последовательность в base64.
            return Convert.ToBase64String(sourceArray);
        }

        private static String AesDecrypt(String source, String key)
        {
            // Получаем строку в виде набора байт.
            var sourceArray = Convert.FromBase64String(source);
            // Получаем ключ в виде набора байт.
            var keyArray = Encoding.UTF8.GetBytes(key);
            // Создаем экземпляр объекта AES.
            var AES = new AesCryptoServiceProvider();
            // Ключ из sha256 пароля.
            var sha256 = SHA256Managed.Create();
            // Устанавливаем ключ. 
            AES.Key = sha256.ComputeHash(keyArray);
            // Вектор инициализации из md5 ключа.
            var md5 = MD5.Create();
            // Устанавливаем вектор инициализации.
            AES.IV = md5.ComputeHash(keyArray);
            // Создаем криптор.
            // Создаем декриптор.
            ICryptoTransform transform = AES.CreateDecryptor();
            // Криптуем строку.
            sourceArray = transform.TransformFinalBlock(sourceArray, 0, sourceArray.Length);
            // Преобразовываем полученную последовательность в base64.
            return Encoding.UTF8.GetString(sourceArray);
        }

        #endregion

        #region Подсветка ключевых слов
        /// <summary>
        /// Подсвечивание слова синим цветом, если оно входит в список ключевых слов.
        /// </summary>
        /// <param name="richTextBox"></param>
        /// <param name="word"></param>
        /// <param name="position"></param>
        private void HighlightWord(RichTextBox richTextBox, String word, Int32 position)
        {
            if (Array.IndexOf(keywords, word) != -1)
            {
                richTextBox.SelectionStart = position;
                richTextBox.SelectionLength = word.Length;
                richTextBox.SelectionColor = Color.Blue;
                richTextBox.SelectionFont = new Font(richTextBox.Font, FontStyle.Bold);
            }
        }

        /// <summary>
        /// Подсвечивание ключевых слов во всем тексте.
        /// </summary>
        /// <param name="richTextBox"></param>
        private void HighlightText(RichTextBox richTextBox)
        {
            var data = richTextBox.Tag as RichTextBoxData;
            // Надо проверить, является ли этот текст исходником c#,
            // для этого проверить расширение пути, хранящегося в имени таба.
            var tab = richTextBox.Parent as TabPage;
            if (tab == null)
                return;
            data.IsCShaprText = String.Compare(Path.GetExtension(tab.Name), ".cs", true) == 0;
            // В настоящий момент уже проводится поиск и выделение ключевых слов.
            if (data.BlockChange)
                return;
            // Позиция курсора.
            var newPos = richTextBox.SelectionStart;
            // Длина выделенного текста.
            var newSelect = richTextBox.SelectionLength;

            data.BlockChange = true;
            try
            {
                // Текст.
                var text = richTextBox.Text;
                // Позиция начала первого слова.
                var firstPos = 0;
                var lastPos = text.Length;

                if (lastPos > 0)
                {
                    richTextBox.BeginUpdate();
                    // Сбрасываем цвет всего текста.
                    richTextBox.SelectionStart = firstPos;
                    richTextBox.SelectionLength = lastPos - firstPos - 1;
                    richTextBox.SelectionColor = Color.Black;
                    richTextBox.SelectionFont = fontDialog.Font;
                    // Если это исходник с# - ищем и выделяем ключевые слова.
                    if (data.IsCShaprText)
                    {
                        // Выделяем отдельные слова, разделенные пробелами и смотрим, являются ли они ключевыми словами.
                        for (var i = firstPos; i < lastPos; i++)
                            if (Char.IsWhiteSpace(text, i))
                            {
                                var word = text.Substring(firstPos, i - firstPos);
                                HighlightWord(richTextBox, word, firstPos);
                                firstPos = i + 1;
                            }
                        // В конце осталось непроверенное слово.
                        if (firstPos < lastPos)
                        {
                            var word = text.Substring(firstPos, lastPos - firstPos);
                            HighlightWord(richTextBox, word, firstPos);
                        }
                    }
                    richTextBox.EndUpdate();

                    // Восстанавливаем позицию курсора и выделение.
                    richTextBox.SelectionStart = newPos;
                    richTextBox.SelectionLength = newSelect;
                }
            }
            finally
            {
                data.BlockChange = false;
            }
            richTextBox.Modified = false;
            // Запоминаем позицию курсора. 
            data.SaveCursorPosition = newPos;
        }

        /// <summary>
        /// Проверка текста при изменении на предмет вхождения ключевых слов.
        /// </summary>
        /// <param name="richTextBox"></param>
        private void CheckForModification(RichTextBox richTextBox)
        {
            var data = richTextBox.Tag as RichTextBoxData;
            // Если это не исходник с# - выходим.
            if (!data.IsCShaprText)
                return;
            // В настоящий момент уже проводится поиск и выделение ключевых слов.
            if (data.BlockChange)
                return;
            // Сохраненная позиция курсора.
            var savePos = data.SaveCursorPosition;
            // Новая позиция курсора.
            var newPos = richTextBox.SelectionStart;
            // Если есть изменения.
            if (richTextBox.Modified)
            {
                data.BlockChange = true;
                try
                {
                    // Длина выделенного текста.
                    var newSelect = richTextBox.SelectionLength;
                    // Текст.
                    var text = richTextBox.Text;
                    // Максимальная позиция в тексте.
                    var maxPos = text.Length - 1;
                    // Если редактор не пустой.
                    if (maxPos >= 0)
                    {
                        // Индексы символов слева и справа от измененного текста.
                        Int32 firstPos, lastPos;
                        if (newPos > savePos)
                        {
                            firstPos = savePos - 1;
                            lastPos = newPos;
                        }
                        else
                        {
                            firstPos = newPos - 1;
                            lastPos = newPos;
                        }
                        // Ищем слева пробельный символ.
                        while (firstPos >= 0 && !Char.IsWhiteSpace(text, firstPos))
                            firstPos--;
                        // Ищем слева пробельный справа.
                        while (lastPos <= maxPos && !Char.IsWhiteSpace(text, lastPos))
                            lastPos++;
                        // Сбрасываем цвет текста для измененной части.
                        richTextBox.SelectionStart = firstPos + 1;
                        richTextBox.SelectionLength = lastPos - firstPos - 1;
                        richTextBox.SelectionColor = Color.Black;
                        richTextBox.SelectionFont = fontDialog.Font;
                        // Позиция начала первого слова.
                        firstPos++;
                        // Выделяем отдельные слова, разделенные пробелами и смотрим, являются ли они ключевыми словами.
                        for (var i = firstPos; i < lastPos; i++)
                            if (Char.IsWhiteSpace(text, i))
                            {
                                var word = text.Substring(firstPos, i - firstPos);
                                HighlightWord(richTextBox, word, firstPos);
                                firstPos = i + 1;
                            }
                        // В конце осталось непроверенное слово.
                        if (firstPos < lastPos)
                        {
                            var word = text.Substring(firstPos, lastPos - firstPos);
                            HighlightWord(richTextBox, word, firstPos);
                        }
                        // Восстанавливаем позицию курсора и выделение.
                        richTextBox.SelectionStart = newPos;
                        richTextBox.SelectionLength = newSelect;
                        // Сбрасываем флаг модификации.
                        richTextBox.Modified = false;
                    }
                }
                finally
                {
                    data.BlockChange = false;
                }
            }
            // Запоминаем позицию курсора. 
            data.SaveCursorPosition = newPos;
        }
        #endregion

        #region Печать
        // From: http://msdn.microsoft.com/en-us/library/ms996492.aspx
        private void printDocument_BeginPrint(object sender, PrintEventArgs e)
        {
            // Start at the beginning of the text
            FirstCharOnPage = 0;
        }

        private void printDocument_EndPrint(object sender, PrintEventArgs e)
        {
            // Clean up cached information
            var richTextBox = GetCurrentRichTextBox();
            if (richTextBox != null)
                richTextBox.FormatRangeDone();
        }

        private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
        {
            // To print the boundaries of the current page margins
            // uncomment the next line:
            // e.Graphics.DrawRectangle(System.Drawing.Pens.Blue, e.MarginBounds);

            // make the RichTextBoxEx calculate and render as much text as will
            // fit on the page and remember the last character printed for the
            // beginning of the next page
            var richTextBox = GetCurrentRichTextBox();
            if (richTextBox == null)
            {
                e.HasMorePages = true;
                return;
            }
            FirstCharOnPage = richTextBox.FormatRange(false, e, FirstCharOnPage, richTextBox.TextLength);
            // check if there are more pages to print
            e.HasMorePages = (FirstCharOnPage < richTextBox.TextLength);
        }
        #endregion

    }

    enum FindKind
    {
        None,
        Find,
        Replace
    }

    /// <summary>
    /// Вспомогательный класс для RichTextBox. Хранит дополнительные данные.
    /// </summary>
    sealed class RichTextBoxData
    {       
        // Является ли текст исходным кодом на C#.
        public Boolean IsCShaprText
        {
            get;
            set;
        }

        // Флаг запрета изменения текста в редакторе.
        public Boolean BlockChange
        {
            get;
            set;
        }

        // Последняя позиция каретки в редакторе.
        public Int32 SaveCursorPosition
        {
            get;
            set;
        }
    }
}
